//===--- FrontendInputsAndOutputs.cpp -------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#include "polarphp/frontend/FrontendInputsAndOutputs.h"

#include "polarphp/ast/DiagnosticsFrontend.h"
#include "polarphp/basic/FileTypes.h"
#include "polarphp/basic/PrimarySpecificPaths.h"
#include "polarphp/basic/Range.h"
#include "polarphp/frontend/FrontendOptions.h"
#include "polarphp/option/Options.h"
#include "polarphp/llparser/Lexer.h"
#include "polarphp/global/NameStrings.h"
#include "llvm/Option/Arg.h"
#include "llvm/Option/ArgList.h"
#include "llvm/Option/Option.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/LineIterator.h"
#include "llvm/Support/Path.h"

using namespace polar;
using namespace llvm::opt;

// Constructors

FrontendInputsAndOutputs::FrontendInputsAndOutputs(
   const FrontendInputsAndOutputs &other) {
   for (InputFile input : other.AllInputs)
      addInput(input);
   IsSingleThreadedWMO = other.IsSingleThreadedWMO;
}

FrontendInputsAndOutputs &FrontendInputsAndOutputs::
operator=(const FrontendInputsAndOutputs &other) {
   clearInputs();
   for (InputFile input : other.AllInputs)
      addInput(input);
   IsSingleThreadedWMO = other.IsSingleThreadedWMO;
   return *this;
}

// All inputs:

std::vector<std::string> FrontendInputsAndOutputs::getInputFilenames() const {
   std::vector<std::string> filenames;
   for (auto &input : AllInputs) {
      filenames.push_back(input.file());
   }
   return filenames;
}

bool FrontendInputsAndOutputs::isReadingFromStdin() const {
   return hasSingleInput() && getFilenameOfFirstInput() == "-";
}

const std::string &FrontendInputsAndOutputs::getFilenameOfFirstInput() const {
   assert(hasInputs());
   const InputFile &inp = AllInputs[0];
   const std::string &f = inp.file();
   assert(!f.empty());
   return f;
}

bool FrontendInputsAndOutputs::forEachInput(
   llvm::function_ref<bool(const InputFile &)> fn) const {
   for (const InputFile &input : AllInputs)
      if (fn(input))
         return true;
   return false;
}

// Primaries:

const InputFile &FrontendInputsAndOutputs::firstPrimaryInput() const {
   assert(!PrimaryInputsInOrder.empty());
   return AllInputs[PrimaryInputsInOrder.front()];
}

const InputFile &FrontendInputsAndOutputs::lastPrimaryInput() const {
   assert(!PrimaryInputsInOrder.empty());
   return AllInputs[PrimaryInputsInOrder.back()];
}

bool FrontendInputsAndOutputs::forEachPrimaryInput(
   llvm::function_ref<bool(const InputFile &)> fn) const {
   for (unsigned i : PrimaryInputsInOrder)
      if (fn(AllInputs[i]))
         return true;
   return false;
}

bool FrontendInputsAndOutputs::forEachNonPrimaryInput(
   llvm::function_ref<bool(const InputFile &)> fn) const {
   return forEachInput([&](const InputFile &f) -> bool {
      return f.isPrimary() ? false : fn(f);
   });
}

void FrontendInputsAndOutputs::assertMustNotBeMoreThanOnePrimaryInput() const {
   assert(!hasMultiplePrimaryInputs() &&
          "have not implemented >1 primary input yet");
}

void FrontendInputsAndOutputs::
assertMustNotBeMoreThanOnePrimaryInputUnlessBatchModeChecksHaveBeenBypassed()
const {
   if (!areBatchModeChecksBypassed())
      assertMustNotBeMoreThanOnePrimaryInput();
}

const InputFile *FrontendInputsAndOutputs::getUniquePrimaryInput() const {
   assertMustNotBeMoreThanOnePrimaryInput();
   return PrimaryInputsInOrder.empty()
          ? nullptr
          : &AllInputs[PrimaryInputsInOrder.front()];
}

const InputFile &
FrontendInputsAndOutputs::getRequiredUniquePrimaryInput() const {
   if (const auto *input = getUniquePrimaryInput())
      return *input;
   llvm_unreachable("No primary when one is required");
}

std::string FrontendInputsAndOutputs::getStatsFileMangledInputName() const {
   // Use the first primary, even if there are multiple primaries.
   // That's enough to keep the file names unique.
   return isWholeModule() ? "all" : firstPrimaryInput().file();
}

bool FrontendInputsAndOutputs::isInputPrimary(StringRef file) const {
   return primaryInputNamed(file) != nullptr;
}

unsigned FrontendInputsAndOutputs::numberOfPrimaryInputsEndingWith(
   StringRef extension) const {
   unsigned n = 0;
   (void)forEachPrimaryInput([&](const InputFile &input) -> bool {
      if (llvm::sys::path::extension(input.file()).endswith(extension))
         ++n;
      return false;
   });
   return n;
}

// Input queries

bool FrontendInputsAndOutputs::shouldTreatAsLLVM() const {
   if (hasSingleInput()) {
      StringRef InputExt = llvm::sys::path::extension(getFilenameOfFirstInput());
      switch (filetypes::lookup_type_for_extension(InputExt)) {
         case filetypes::TY_LLVM_BC:
         case filetypes::TY_LLVM_IR:
            return true;
         default:
            return false;
      }
   }
   return false;
}

bool FrontendInputsAndOutputs::shouldTreatAsModuleInterface() const {
   if (!hasSingleInput())
      return false;

   StringRef InputExt = llvm::sys::path::extension(getFilenameOfFirstInput());
   filetypes::FileTypeId InputType = filetypes::lookup_type_for_extension(InputExt);
   return InputType == filetypes::TY_PHPModuleInterfaceFile;
}

bool FrontendInputsAndOutputs::shouldTreatAsPIL() const {
   if (hasSingleInput()) {
      // If we have exactly one input filename, and its extension is "sil",
      // treat the input as PIL.
      StringRef extension = llvm::sys::path::extension(getFilenameOfFirstInput());
      return filetypes::lookup_type_for_extension(extension) == filetypes::TY_PIL;
   }
   // If we have one primary input and it's a filename with extension "sil",
   // treat the input as PIL.
   const unsigned silPrimaryCount = numberOfPrimaryInputsEndingWith(
      filetypes::get_extension(filetypes::TY_PIL));
   if (silPrimaryCount == 0)
      return false;
   if (silPrimaryCount == primaryInputCount()) {
      // Not clear what to do someday with multiple primaries
      assertMustNotBeMoreThanOnePrimaryInput();
      return true;
   }
   llvm_unreachable("Either all primaries or none must end with .sil");
}

bool FrontendInputsAndOutputs::areAllNonPrimariesPIB() const {
   for (const InputFile &input : AllInputs) {
      if (input.isPrimary())
         continue;
      StringRef extension = llvm::sys::path::extension(input.file());
      if (filetypes::lookup_type_for_extension(extension) != filetypes::TY_PIB) {
         return false;
      }
   }
   return true;
}

bool FrontendInputsAndOutputs::verifyInputs(DiagnosticEngine &diags,
                                            bool treatAsPIL,
                                            bool isREPLRequested,
                                            bool isNoneRequested) const {
   if (isREPLRequested) {
      if (hasInputs()) {
         diags.diagnose(SourceLoc(), diag::error_repl_requires_no_input_files);
         return true;
      }
   } else if (treatAsPIL) {
      if (isWholeModule()) {
         if (inputCount() != 1) {
            diags.diagnose(SourceLoc(), diag::error_mode_requires_one_input_file);
            return true;
         }
      } else {
         assertMustNotBeMoreThanOnePrimaryInput();
         // If we have the PIL as our primary input, we can waive the one file
         // requirement as long as all the other inputs are PIBs.
         if (!areAllNonPrimariesPIB()) {
            diags.diagnose(SourceLoc(),
                           diag::error_mode_requires_one_sil_multi_sib);
            return true;
         }
      }
   } else if (!isNoneRequested && !hasInputs()) {
      diags.diagnose(SourceLoc(), diag::error_mode_requires_an_input_file);
      return true;
   }
   return false;
}

// Changing inputs

void FrontendInputsAndOutputs::clearInputs() {
   AllInputs.clear();
   PrimaryInputsByName.clear();
   PrimaryInputsInOrder.clear();
}

void FrontendInputsAndOutputs::addInput(const InputFile &input) {
   const unsigned index = AllInputs.size();
   AllInputs.push_back(input);
   if (input.isPrimary()) {
      PrimaryInputsInOrder.push_back(index);
      PrimaryInputsByName.insert(std::make_pair(AllInputs.back().file(), index));
   }
}

void FrontendInputsAndOutputs::addInputFile(StringRef file,
                                            llvm::MemoryBuffer *buffer) {
   addInput(InputFile(file, false, buffer));
}

void FrontendInputsAndOutputs::addPrimaryInputFile(StringRef file,
                                                   llvm::MemoryBuffer *buffer) {
   addInput(InputFile(file, true, buffer));
}

// Outputs

unsigned FrontendInputsAndOutputs::countOfInputsProducingMainOutputs() const {
   return isSingleThreadedWMO()
          ? 1
          : hasPrimaryInputs() ? primaryInputCount() : inputCount();
}

const InputFile &FrontendInputsAndOutputs::firstInputProducingOutput() const {
   return isSingleThreadedWMO()
          ? firstInput()
          : hasPrimaryInputs() ? firstPrimaryInput() : firstInput();
}

const InputFile &FrontendInputsAndOutputs::lastInputProducingOutput() const {
   return isSingleThreadedWMO()
          ? firstInput()
          : hasPrimaryInputs() ? lastPrimaryInput() : lastInput();
}

bool FrontendInputsAndOutputs::forEachInputProducingAMainOutputFile(
   llvm::function_ref<bool(const InputFile &)> fn) const {
   return isSingleThreadedWMO()
          ? fn(firstInput())
          : hasPrimaryInputs() ? forEachPrimaryInput(fn) : forEachInput(fn);
}

void FrontendInputsAndOutputs::setMainAndSupplementaryOutputs(
   ArrayRef<std::string> outputFiles,
   ArrayRef<SupplementaryOutputPaths> supplementaryOutputs) {
   if (AllInputs.empty()) {
      assert(outputFiles.empty() && "Cannot have outputs without inputs");
      assert(supplementaryOutputs.empty() &&
             "Cannot have supplementary outputs without inputs");
      return;
   }
   if (hasPrimaryInputs()) {
      const auto N = primaryInputCount();
      assert(outputFiles.size() == N && "Must have one main output per primary");
      assert(supplementaryOutputs.size() == N &&
             "Must have one set of supplementary outputs per primary");
      (void)N;

      unsigned i = 0;
      for (auto &input : AllInputs) {
         if (input.isPrimary()) {
            input.setPrimarySpecificPaths(PrimarySpecificPaths(
               outputFiles[i], input.file(), supplementaryOutputs[i]));
            ++i;
         }
      }
      return;
   }
   assert(supplementaryOutputs.size() == 1 &&
          "WMO only ever produces one set of supplementary outputs");
   if (outputFiles.size() == 1) {
      AllInputs.front().setPrimarySpecificPaths(PrimarySpecificPaths(
         outputFiles.front(), firstInputProducingOutput().file(),
         supplementaryOutputs.front()));
      return;
   }
   assert(outputFiles.size() == AllInputs.size() &&
          "Multi-threaded WMO requires one main output per input");
   for (auto i : indices(AllInputs))
      AllInputs[i].setPrimarySpecificPaths(PrimarySpecificPaths(
         outputFiles[i], outputFiles[i],
         i == 0 ? supplementaryOutputs.front() : SupplementaryOutputPaths()));
}

std::vector<std::string> FrontendInputsAndOutputs::copyOutputFilenames() const {
   std::vector<std::string> outputs;
   (void)forEachInputProducingAMainOutputFile(
      [&](const InputFile &input) -> bool {
         outputs.push_back(input.outputFilename());
         return false;
      });
   return outputs;
}

void FrontendInputsAndOutputs::forEachOutputFilename(
   llvm::function_ref<void(StringRef)> fn) const {
   (void)forEachInputProducingAMainOutputFile(
      [&](const InputFile &input) -> bool {
         fn(input.outputFilename());
         return false;
      });
}

std::string FrontendInputsAndOutputs::getSingleOutputFilename() const {
   assertMustNotBeMoreThanOnePrimaryInputUnlessBatchModeChecksHaveBeenBypassed();
   return hasInputs() ? lastInputProducingOutput().outputFilename()
                      : std::string();
}

bool FrontendInputsAndOutputs::isOutputFilenameStdout() const {
   return getSingleOutputFilename() == "-";
}

bool FrontendInputsAndOutputs::isOutputFileDirectory() const {
   return hasNamedOutputFile() &&
          llvm::sys::fs::is_directory(getSingleOutputFilename());
}

bool FrontendInputsAndOutputs::hasNamedOutputFile() const {
   return hasInputs() && !isOutputFilenameStdout();
}

// Supplementary outputs

unsigned
FrontendInputsAndOutputs::countOfFilesProducingSupplementaryOutput() const {
   return hasPrimaryInputs() ? primaryInputCount() : hasInputs() ? 1 : 0;
}

bool FrontendInputsAndOutputs::forEachInputProducingSupplementaryOutput(
   llvm::function_ref<bool(const InputFile &)> fn) const {
   return hasPrimaryInputs() ? forEachPrimaryInput(fn)
                             : hasInputs() ? fn(firstInput()) : false;
}

bool FrontendInputsAndOutputs::hasSupplementaryOutputPath(
   llvm::function_ref<const std::string &(const SupplementaryOutputPaths &)>
   extractorFn) const {
   return forEachInputProducingSupplementaryOutput([&](const InputFile &input)
                                                      -> bool {
      return !extractorFn(input.getPrimarySpecificPaths().supplementaryOutputs)
         .empty();
   });
}

bool FrontendInputsAndOutputs::hasDependenciesPath() const {
   return hasSupplementaryOutputPath(
      [](const SupplementaryOutputPaths &outs) -> const std::string & {
         return outs.DependenciesFilePath;
      });
}
bool FrontendInputsAndOutputs::hasReferenceDependenciesPath() const {
   return hasSupplementaryOutputPath(
      [](const SupplementaryOutputPaths &outs) -> const std::string & {
         return outs.ReferenceDependenciesFilePath;
      });
}
bool FrontendInputsAndOutputs::hasPHPRangesPath() const {
   return hasSupplementaryOutputPath(
      [](const SupplementaryOutputPaths &outs) -> const std::string & {
         return outs.PHPRangesFilePath;
      });
}
bool FrontendInputsAndOutputs::hasCompiledSourcePath() const {
   return hasSupplementaryOutputPath(
      [](const SupplementaryOutputPaths &outs) -> const std::string & {
         return outs.CompiledSourceFilePath;
      });
}
bool FrontendInputsAndOutputs::hasObjCHeaderOutputPath() const {
   return hasSupplementaryOutputPath(
      [](const SupplementaryOutputPaths &outs) -> const std::string & {
         return outs.ObjCHeaderOutputPath;
      });
}
bool FrontendInputsAndOutputs::hasLoadedModuleTracePath() const {
   return hasSupplementaryOutputPath(
      [](const SupplementaryOutputPaths &outs) -> const std::string & {
         return outs.LoadedModuleTracePath;
      });
}
bool FrontendInputsAndOutputs::hasModuleOutputPath() const {
   return hasSupplementaryOutputPath(
      [](const SupplementaryOutputPaths &outs) -> const std::string & {
         return outs.ModuleOutputPath;
      });
}
bool FrontendInputsAndOutputs::hasModuleDocOutputPath() const {
   return hasSupplementaryOutputPath(
      [](const SupplementaryOutputPaths &outs) -> const std::string & {
         return outs.ModuleDocOutputPath;
      });
}
bool FrontendInputsAndOutputs::hasModuleSourceInfoOutputPath() const {
   return hasSupplementaryOutputPath(
      [](const SupplementaryOutputPaths &outs) -> const std::string & {
         return outs.ModuleSourceInfoOutputPath;
      });
}
bool FrontendInputsAndOutputs::hasModuleInterfaceOutputPath() const {
   return hasSupplementaryOutputPath(
      [](const SupplementaryOutputPaths &outs) -> const std::string & {
         return outs.ModuleInterfaceOutputPath;
      });
}
bool FrontendInputsAndOutputs::hasTBDPath() const {
   return hasSupplementaryOutputPath(
      [](const SupplementaryOutputPaths &outs) -> const std::string & {
         return outs.TBDPath;
      });
}

bool FrontendInputsAndOutputs::hasDependencyTrackerPath() const {
   return hasDependenciesPath() || hasReferenceDependenciesPath() ||
          hasLoadedModuleTracePath();
}

const PrimarySpecificPaths &
FrontendInputsAndOutputs::getPrimarySpecificPathsForAtMostOnePrimary() const {
   assertMustNotBeMoreThanOnePrimaryInputUnlessBatchModeChecksHaveBeenBypassed();
   static auto emptyPaths = PrimarySpecificPaths();
   return hasInputs() ? firstInputProducingOutput().getPrimarySpecificPaths()
                      : emptyPaths;
}

const PrimarySpecificPaths &
FrontendInputsAndOutputs::getPrimarySpecificPathsForPrimary(
   StringRef filename) const {
   const InputFile *f = primaryInputNamed(filename);
   return f->getPrimarySpecificPaths();
}

const InputFile *
FrontendInputsAndOutputs::primaryInputNamed(StringRef name) const {
   assert(!name.empty() && "input files have names");
   StringRef correctedFile =
      InputFile::convertBufferNameFromLLVM_getFileOrSTDIN_toSwiftConventions(
         name);
   auto iterator = PrimaryInputsByName.find(correctedFile);
   if (iterator == PrimaryInputsByName.end())
      return nullptr;
   const InputFile *f = &AllInputs[iterator->second];
   assert(f->isPrimary() && "PrimaryInputsByName should only include primries");
   return f;
}
